{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:31.139921Z",
     "start_time": "2025-01-20T03:55:25.296542Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)  #设备是cuda:0，即GPU，如果没有GPU则是cpu\n",
    "\n",
    "seed = 42\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 数据准备",
   "id": "1073bc64c567ba13"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:33.572245Z",
     "start_time": "2025-01-20T03:55:31.139921Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "from torch.utils.data import random_split\n",
    "\n",
    "# 训练集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# 测试集\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 这里用 random_split 按照 11 : 1 的比例来划分数据集\n",
    "train_ds, val_ds = random_split(train_ds, [55000, 5000], torch.Generator().manual_seed(seed))\n",
    "\n",
    "\n"
   ],
   "id": "55788f25672e1ebe",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "",
   "id": "4fffd32f199496a1"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.741075Z",
     "start_time": "2025-01-20T03:55:33.572245Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torchvision.transforms import Normalize\n",
    "\n",
    "\n",
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "_mean, _std = cal_mean_std(train_ds)\n",
    "transforms = nn.Sequential(\n",
    "    Normalize(mean=_mean, std=_std),\n",
    ")\n",
    "\n",
    "_mean, _std"
   ],
   "id": "f17a4198ee382763",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([0.2856]), tensor([0.3202]))"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.747453Z",
     "start_time": "2025-01-20T03:55:40.743083Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从数据集到dataloader\n",
    "batch_size = 32\n",
    "# num_workers 多线程加载数据，提高效率\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=2)\n",
    "val_loader = torch.utils.data.DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=2)\n",
    "test_loader = torch.utils.data.DataLoader(test_ds, batch_size=batch_size, shuffle=False, num_workers=2)"
   ],
   "id": "ccdf8026c7024c31",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "b5e4f17ae389c008"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.766611Z",
     "start_time": "2025-01-20T03:55:40.749586Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self, activation=\"relu\"):\n",
    "        super(CNN, self).__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        self.flattener = nn.Flatten()  # 展平层\n",
    "        # 卷积层\n",
    "        # nn.Conv2d:这是 PyTorch 中的二维卷积层，用于处理二维输入数据（如图像）。\n",
    "        # in_channels=1：输入数据的通道数。\n",
    "        #               对于灰度图像，通道数为 1；对于 RGB 图像，通道数为 3\n",
    "        # out_channels=32：输出数据的通道数，即卷积核的数量。\n",
    "        #                 每个卷积核会提取输入数据的不同特征\n",
    "        # kernel_size=3：卷积核的大小，通常是一个正方形。\n",
    "        #               kernel_size=3 表示卷积核的大小为 3x3。\n",
    "        # padding=1：在输入数据的边缘填充 0 的层数。\n",
    "        #              对于卷积层，padding=1 表示在输入数据的每一边填充 1 层 0，确保输出数据的空间尺寸与输入数据相同。\n",
    "        #输入x(32,1,28,28) 输出x(32,32,28,28)        \n",
    "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1)\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1)\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 池化层\n",
    "        # nn.MaxPool2d:这是 PyTorch 中的最大池化层，用于对输入数据进行降采样。\n",
    "        # kernel_size=2：池化核的大小，通常是一个正方形。\n",
    "        self.pool = nn.MaxPool2d(2, 2)  #池化核大小为2（2*2），步长为2\n",
    "        # 全连接层\n",
    "        # (32,128,3,3)->(32,128*3*3)\n",
    "        self.fc1 = nn.Linear(128 * 3 * 3, 128)\n",
    "        self.fc2 = nn.Linear(128, 10)  # 输出层，10个类别 #输出尺寸（32,10）\n",
    "\n",
    "        self.init_weights()  # 初始化权重\n",
    "\n",
    "    def init_weights(self):  # 初始化权重\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "        #          conv1          conv2         pool  \n",
    "        # 32*1*28*28 -> 32*32*28*28-> 32*32*28*28->32*32*14*14\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "        # print(x.shape)\n",
    "        #           conv3          conv4         pool  \n",
    "        # 32*32*14*14 -> 32*64*14*14 -> 32*64*14*14 -> 32*64*7*7\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "        # print(x.shape)\n",
    "        #           conv5          conv6         pool  \n",
    "        # 32*64*7*7 -> 32*128*7*7 -> 32*128*7*7 -> 32*128*3*3\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "        # print(x.shape)\n",
    "\n",
    "        # 32*128*3*3 ->32*(128*3*3)\n",
    "        x = self.flattener(x)  #展平\n",
    "        # 32*(128*3*3)->32*128\n",
    "        x = act(self.fc1(x))\n",
    "        # 32*128->32*10\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")  # 打印模型的参数信息"
   ],
   "id": "d3016623476b4fd4",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 288\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 147456\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T09:00:10.294872Z",
     "start_time": "2025-01-20T09:00:10.285016Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 计算模型总参数量\n",
    "def count_parameters(model):\n",
    "    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "\n",
    "\n",
    "count_parameters(CNN())"
   ],
   "id": "792d1b69c4dac738",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "435306"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 训练模型",
   "id": "8fb5dfcd77513c03"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.885481Z",
     "start_time": "2025-01-20T03:55:40.768618Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率\n"
   ],
   "id": "24550945260b9755",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## Save Best Model",
   "id": "6fd06a23a5bccc23"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.892093Z",
     "start_time": "2025-01-20T03:55:40.885481Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"02_cnn_best.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ],
   "id": "e93aec97aec45c4e",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## Early Stopping",
   "id": "3f9a004729448217"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.899168Z",
     "start_time": "2025-01-20T03:55:40.892093Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "d8fdc303a4d27c00",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 训练",
   "id": "b56dab87e8ff6485"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.909274Z",
     "start_time": "2025-01-20T03:55:40.899168Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ],
   "id": "2602ccd3cb633f69",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "",
   "id": "d111e077fd6e015"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T03:55:40.921427Z",
     "start_time": "2025-01-20T03:55:40.909274Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 100\n",
    "\n",
    "activation = \"selu\"  # 激活函数\n",
    "model = CNN(activation)  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=500, save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.01)"
   ],
   "id": "8b42caff09582576",
   "outputs": [],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T04:11:56.107033Z",
     "start_time": "2025-01-20T03:55:40.921427Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=1000\n",
    ")"
   ],
   "id": "1b4d7f1cfe612c71",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/171900 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "f1b289c1720443b4b9363b5e36f5ee6e"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 12 / global_step 22000\n"
     ]
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:31:02.765661Z",
     "start_time": "2025-01-20T08:31:02.521874Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    print(train_df)\n",
    "    print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1], 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ],
   "id": "de28437c6058f608",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           loss      acc\n",
      "step                    \n",
      "0      2.560387  0.15625\n",
      "500    0.505625  0.78125\n",
      "1000   0.529447  0.81250\n",
      "1500   0.287510  0.87500\n",
      "2000   0.180411  0.93750\n",
      "2500   0.368301  0.84375\n",
      "3000   0.391575  0.84375\n",
      "3500   0.169287  0.90625\n",
      "4000   0.150991  0.93750\n",
      "4500   0.387178  0.84375\n",
      "5000   0.511894  0.84375\n",
      "5500   0.174159  0.90625\n",
      "6000   0.252671  0.90625\n",
      "6500   0.281656  0.90625\n",
      "7000   0.159363  0.93750\n",
      "7500   0.147521  0.90625\n",
      "8000   0.172746  0.93750\n",
      "8500   0.201624  0.87500\n",
      "9000   0.270963  0.90625\n",
      "9500   0.345347  0.87500\n",
      "10000  0.058041  1.00000\n",
      "10500  0.070118  1.00000\n",
      "11000  0.119837  0.90625\n",
      "11500  0.090361  0.96875\n",
      "12000  0.049917  1.00000\n",
      "12500  0.178791  0.90625\n",
      "13000  0.134503  0.93750\n",
      "13500  0.054468  1.00000\n",
      "14000  0.035181  1.00000\n",
      "14500  0.125842  0.93750\n",
      "15000  0.246915  0.90625\n",
      "15500  0.145485  0.93750\n",
      "16000  0.075191  1.00000\n",
      "16500  0.117435  0.96875\n",
      "17000  0.107574  0.93750\n",
      "17500  0.037272  1.00000\n",
      "18000  0.142734  0.96875\n",
      "18500  0.096710  0.96875\n",
      "19000  0.025775  1.00000\n",
      "19500  0.065164  0.96875\n",
      "20000  0.116258  0.93750\n",
      "20500  0.283784  0.90625\n",
      "21000  0.052566  0.96875\n",
      "21500  0.081949  0.93750\n",
      "22000  0.025895  1.00000\n",
      "           loss     acc\n",
      "step                   \n",
      "0      2.406988  0.1210\n",
      "1000   0.460007  0.8332\n",
      "2000   0.355049  0.8730\n",
      "3000   0.354996  0.8726\n",
      "4000   0.285591  0.8970\n",
      "5000   0.282278  0.8992\n",
      "6000   0.289269  0.8972\n",
      "7000   0.266269  0.9028\n",
      "8000   0.277054  0.8970\n",
      "9000   0.265750  0.9030\n",
      "10000  0.267339  0.9032\n",
      "11000  0.273617  0.8996\n",
      "12000  0.232387  0.9142\n",
      "13000  0.240357  0.9130\n",
      "14000  0.232941  0.9168\n",
      "15000  0.225458  0.9202\n",
      "16000  0.237195  0.9150\n",
      "17000  0.239324  0.9178\n",
      "18000  0.243065  0.9190\n",
      "19000  0.252075  0.9142\n",
      "20000  0.263955  0.9124\n",
      "21000  0.265613  0.9132\n",
      "22000  0.277421  0.9112\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 18
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T04:11:56.298185Z",
     "start_time": "2025-01-20T04:11:56.292674Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"train\"][-5:]",
   "id": "775b113bb78383fa",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.07604482024908066, 'acc': 0.9375, 'step': 21996},\n",
       " {'loss': 0.08883718401193619, 'acc': 0.96875, 'step': 21997},\n",
       " {'loss': 0.0858546793460846, 'acc': 0.96875, 'step': 21998},\n",
       " {'loss': 0.14792820811271667, 'acc': 0.9375, 'step': 21999},\n",
       " {'loss': 0.02589508146047592, 'acc': 1.0, 'step': 22000}]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T04:11:56.304067Z",
     "start_time": "2025-01-20T04:11:56.298185Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"val\"][-10:]",
   "id": "dbb21c010aaef95e",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'loss': 0.2403574894912019, 'acc': 0.913, 'step': 13000},\n",
       " {'loss': 0.2329409062316653, 'acc': 0.9168, 'step': 14000},\n",
       " {'loss': 0.22545802934913878, 'acc': 0.9202, 'step': 15000},\n",
       " {'loss': 0.23719456628154797, 'acc': 0.915, 'step': 16000},\n",
       " {'loss': 0.23932373073696497, 'acc': 0.9178, 'step': 17000},\n",
       " {'loss': 0.24306544991044007, 'acc': 0.919, 'step': 18000},\n",
       " {'loss': 0.25207541512316506, 'acc': 0.9142, 'step': 19000},\n",
       " {'loss': 0.2639545609189826, 'acc': 0.9124, 'step': 20000},\n",
       " {'loss': 0.2656133650730892, 'acc': 0.9132, 'step': 21000},\n",
       " {'loss': 0.27742123406211355, 'acc': 0.9112, 'step': 22000}]"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 评估以及上线",
   "id": "1fa2f200745f7b63"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T04:11:56.313512Z",
     "start_time": "2025-01-20T04:11:56.304067Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = CNN(activation)  #上线时加载模型\n",
    "model = model.to(device)  # 将模型移到GPU上"
   ],
   "id": "b106406c03208c15",
   "outputs": [],
   "execution_count": 15
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T04:13:35.175785Z",
     "start_time": "2025-01-20T04:13:26.985587Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/02_cnn_best.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, test_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ],
   "id": "26cdacda63d5388e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.2471, Test acc: 0.9147\n"
     ]
    }
   ],
   "execution_count": 17
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
